package com.chat.config.web.webSocket.handler;

import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.chat.comment.constant.WebSocketConstant;
import com.chat.module.user.service.impl.UserServiceImpl;
import com.comment.pojo.domain.chat.WsMessage;
import com.chat.module.client.ClientMessageServiceImpl;
import com.chat.config.web.webSocket.WebSocketInfoService;

import com.comment.pojo.domain.chat.UserLinkInfo;
import com.comment.util.JwtUtils;
import com.comment.util.ip.SearcherIp;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.net.InetSocketAddress;
import java.util.*;

import static com.chat.config.web.webSocket.SessionConstant.CHANEL_INFO_KEY;
import static com.chat.config.web.webSocket.handler.WebSocketChannelManager.channelGroup;
import static com.chat.config.web.webSocket.handler.WebSocketChannelManager.channelMap;


@Slf4j
@Service
public class WebSocketSimpleChannelInboundHandler extends SimpleChannelInboundHandler<Object> {
    // 用户上线下线操作这个Map集合
    public static Map<String, Object> ext = new HashMap<String, Object>();
    private static Long sendUserId;

    // WebSocket 握手工厂类
    private final WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(WebSocketConstant.WEB_SOCKET_URL, null, false);
    private WebSocketServerHandshaker handshaker;
    private final WebSocketInfoService websocketInfoService = new WebSocketInfoService();

    private final UserServiceImpl friendsService = SpringUtil.getBean(UserServiceImpl.class);


    /**
     * 处理客户端与服务端之间的 websocket 业务
     */
    private void handWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 判断是否是关闭 websocket 的指令
        if (frame instanceof CloseWebSocketFrame) {
            // 关闭握手
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            websocketInfoService.clearSession(ctx.channel());
            return;
        }
        // 判断是否是ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 判断是否Pong消息
        if (frame instanceof PongWebSocketFrame) {
            ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 判断是否是二进制消息，如果是二进制消息，抛出异常
        if (!(frame instanceof TextWebSocketFrame)) {
            System.out.println("目前我们不支持二进制消息");
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            throw new RuntimeException("【" + this.getClass().getName() + "】不支持消息");
        }
        // 获取并解析客户端向服务端发送的 json 消息

        UserLinkInfo userLinkInfo = ctx.channel().attr(CHANEL_INFO_KEY).get();
        String message = ((TextWebSocketFrame) frame).text();
        WsMessage wsMessage = JSONObject.parseObject(message, WsMessage.class);
        wsMessage.setSendTime(System.currentTimeMillis());
        wsMessage.setSendUserId(userLinkInfo.getUserId());
        // 客户端发送到服务端的消息
        ClientMessageServiceImpl clientMessageService = SpringUtil.getBean(ClientMessageServiceImpl.class);
        clientMessageService.messageLinkByCode(ctx, wsMessage);
    }


    /**
     * 客户端与服务端创建连接的时候调用
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // 创建新的 WebSocket 连接，保存当前 channel

        log.info("————客户端与服务端连接开启————");
    }

    /**
     * 用户下线
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        UserLinkInfo userLinkInfo = ctx.channel().attr(CHANEL_INFO_KEY).get();
        log.info(String.format("用户下线：%s", userLinkInfo));
        websocketInfoService.clearSession(ctx.channel());
    }

    /**
     * 服务端接收客户端发送过来的数据结束之后调用
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 通常用于确保所有的消息都被及时地发送给了对端，而不会因为缓冲区的原因延迟发送。
        ctx.flush();
    }

    /**
     * 工程出现异常的时候调用
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("异常:", cause);
        ctx.close();
    }

    /**
     * 服务端处理客户端websocket请求的核心方法
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        if (o instanceof FullHttpRequest) {
            // 处理客户端向服务端发起 http 请求的业务
            handHttpRequest(channelHandlerContext, (FullHttpRequest) o);
        } else if (o instanceof WebSocketFrame) {
            // 处理客户端与服务端之间的 websocket 业务
            handWebsocketFrame(channelHandlerContext, (WebSocketFrame) o);
        }
    }

    /**
     * 处理客户端向服务端发起 http 握手请求的业务
     * WebSocket在建立握手时，数据是通过HTTP传输的。但是建立之后，在真正传输时候是不需要HTTP协议的。
     * <p>
     * WebSocket 连接过程：
     * 首先，客户端发起http请求，经过3次握手后，建立起TCP连接；http请求里存放WebSocket支持的版本号等信息，如：Upgrade、Connection、WebSocket-Version等；
     * 然后，服务器收到客户端的握手请求后，同样采用HTTP协议回馈数据；
     * 最后，客户端收到连接成功的消息后，开始借助于TCP传输信道进行全双工通信。
     */
    private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        // 如果请求失败或者该请求不是客户端向服务端发起的 http 请求，则响应错误信息
        if (!request.decoderResult().isSuccess()
                || !("websocket".equals(request.headers().get("Upgrade")))) {
            // code ：400
            sendHttpResponse(ctx, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        // 新建一个握手
        handshaker = factory.newHandshaker(request);
        if (handshaker == null) {
            // 如果为空，返回响应：不受支持的 websocket 版本
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            // 获取远程地址
            Channel channel = ctx.channel();
            String uri = request.uri();
            Map<String, String> params = HttpUtil.decodeParamMap(uri, CharsetUtil.UTF_8);
            String tokenStr = params.get("token");
            Long playerId = JwtUtils.getPlayerId(tokenStr);
            InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress();
            String ip = inetSocketAddress.getHostString();
            Integer port = inetSocketAddress.getPort();

            // 初始化用户连接信息
            UserLinkInfo init = UserLinkInfo.init(playerId);
            init.setLastLinkIp(ip);
            init.setLastLinkPort(port);

            channel.attr(CHANEL_INFO_KEY).set(init);
            channelGroup.add(channel);
            channelMap.put(playerId, channel);
            handshaker.handshake(channel, request);

            Object userId = channel.attr(CHANEL_INFO_KEY).get().getUserId();

            String portStr = String.valueOf(port);
            String source = SearcherIp.searcherId(ip);
            log.info("————新增用户：{}({}:{}-{}),在线数量:{}————",
                    userId, ip, portStr, source, channelGroup.size()
            );
            friendsService.tryInit(playerId);
        }
    }


    /**
     * 服务端向客户端响应消息
     */
    private void sendHttpResponse(ChannelHandlerContext ctx, DefaultFullHttpResponse response) {
        if (response.status().code() != 200) {
            // 创建源缓冲区
            ByteBuf byteBuf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
            // 将源缓冲区的数据传送到此缓冲区
            response.content().writeBytes(byteBuf);
            // 释放源缓冲区
            byteBuf.release();
        }
        // 写入请求，服务端向客户端发送数据
        ChannelFuture channelFuture = ctx.channel().writeAndFlush(response);
        if (response.status().code() != 200) {
            channelFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }
}
